#include "TFrame.h"
#include <signal.h>

/*********** function implementation *************/
void WorkQueue::setNumThreads(int number) {// set the number of threads to the number //
  num_threads = number;
}

void* run_thread (void* x) {
  WorkQueue* Q = (WorkQueue*)x;
  while (true) {
  start:
    pthread_mutex_lock(&Q->qmutex); // acquire the mutex on the queue //
    if (Q->workq.empty()) {// queue is empty wait for it to be filled 
      // wait on conditional variables //
      pthread_mutex_unlock(&Q->qmutex);
      pthread_mutex_lock(&Q->cmutex);
      Q->count++;
      if(Q->count == Q->num_threads) {
	pthread_cond_signal(&Q->done);
      }
      pthread_cond_wait(&Q->empty_cond, &Q->cmutex);
      Q->count--;
      pthread_mutex_unlock(&Q->cmutex);
      pthread_mutex_lock(&Q->qmutex);
    }
    Task * myTask;
    myTask = Q->workq.front();
    if (myTask == NULL) {
      pthread_mutex_unlock(&Q->qmutex);
      goto start;
    }
    Q->workq.pop();
    pthread_mutex_unlock(&Q->qmutex);
    myTask->run();
    pthread_mutex_lock(&myTask->dmutex);
    myTask->done = true;
    pthread_mutex_unlock(&myTask->dmutex);
    pthread_cond_broadcast(&myTask->tdone);
    std::cout<<"---> Task done\n";
  }
  pthread_exit(NULL);  
}

int WorkQueue::initThreads() {
  if (!num_threads) {
    std::cout<<"You must initialize the number of threads!\n";
    return -1;
  }
  pool = new pthread_t[num_threads];
  for (int i=0; i < num_threads; i++) {
    pthread_create(&pool[i], NULL, run_thread, (void*)this);
  } 
  return 0;
}

int WorkQueue::addTask(Task* t) {
  t->done = false;
  pthread_mutex_init(&t->dmutex, NULL);
  pthread_cond_init(&t->tdone, NULL);
  pthread_mutex_lock(&qmutex);
  if (workq.empty()) {
    workq.push(t);
    pthread_mutex_unlock(&qmutex);
    if (!pthread_cond_broadcast(&empty_cond)) {
      return 0;
    }else {
      return -1;
    }
  }
  workq.push(t);
  pthread_mutex_unlock(&qmutex);
  return 0;
}

int WorkQueue::wait_for_threads() {
  if (!num_threads)
    return -1;
  pthread_mutex_lock(&cmutex);
  pthread_mutex_lock(&qmutex);
  if (count == num_threads && workq.empty()) {
    pthread_mutex_unlock(&qmutex);
    pthread_mutex_unlock(&cmutex);
    printf("All threads are done!\n");
    return 0;
  } else {
    pthread_mutex_unlock(&qmutex);
    pthread_cond_wait(&done, &cmutex);
    pthread_mutex_unlock(&cmutex);
    printf("All threads are done!\n");
    return 0;
  }
}

void wait_for_task(Task* t) {// wait for a specific task to be done //
  pthread_mutex_lock(&t->dmutex);
  if (!t->done) {// not done yet //
    pthread_cond_wait(&t->tdone, &t->dmutex);// wait for the signal to be done //
    pthread_mutex_unlock(&t->dmutex);
    return;
  }else {
    pthread_mutex_unlock(&t->dmutex); //simply leave //
    return;
  }
}

/******* End of function implementation ***********/
